﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.ComponentModel;

namespace Noris.Schedule.UI.Components
{
    #region CLASS TrackBarRatio
    public class TrackBarRatio : TrackBar
	{
		public TrackBarRatio()
		{
            this.TickFrequency = 0;
            this.ValueChanged += new EventHandler(_ValueChanged);
        }
		#region PROPERTY A EVENTY
		/// <summary>
		/// Rozlišení posuvníku = na kolik dílků se posuvník dělí. Určuje, jaké nejmenší změny posuvník může generovat.
        /// Pokud je Resolution == 10, pak Ratio bude 0 - 0.1 - 0.2 - 0.3 - ... - 0.9 - 1.0, nikdy nebude jemněnší hodnota (například nelze nastavit 0.15).
		/// </summary>
        [Description("Rozlišení posuvníku = na kolik dílků se posuvník dělí. Určuje, jaké nejmenší změny posuvník může generovat.\r\nPokud je Resolution == 10, pak Ratio bude 0 - 0.1 - 0.2 - 0.3 - ... - 0.9 - 1.0, nikdy nebude jemněnší hodnota (například nelze nastavit 0.15).")]
		[Category("Data")]
		public int Resolution
		{
            get { this._ResolutionGet(); return this._Resolution; }
            set { this._Resolution = value; this._ResolutionSet(); }
		}
		/// <summary>
		/// Aktuální hodnota vyjádřená v rozmezí 0 až 1.
        /// Nastavení hodnoty se promítne jak do controlu, tak dojde k vyvolání eventu RatioChanged.
		/// </summary>
		[Description("Aktuální hodnota vyjádřená v rozmezí 0 až 1.\r\nNastavení hodnoty se promítne jak do controlu, tak dojde k vyvolání eventu RatioChanged.")]
		[Category("Data")]
		public double? Ratio
		{
			get { this._RatioGet(); return _Ratio; }
			set { this._SilentChange = false; this._Ratio = value; this._RatioSet(); }
		}
		/// <summary>
        /// Aktuální hodnota vyjádřená v rozmezí 0 až 1.
        /// Nastavení hodnoty se promítne do controlu, ale nedojde k vyvolání eventu RatioChanged.
        /// </summary>
		[Description("Aktuální hodnota vyjádřená v rozmezí 0 až 1.\r\nNastavení hodnoty se promítne do controlu, ale nedojde k vyvolání eventu RatioChanged.")]
		[Category("Data")]
		public double? RatioSilent
		{
			get { this._RatioGet(); return _Ratio; }
			set { this._SilentChange = true; this._Ratio = value; this._RatioSet(); this._SilentChange = false; }
		}
        /// <summary>
        /// Událost vyvolaná v průběhu změny hodnoty Ratio posuvníku.
        /// </summary>
        [Browsable(true)]
        [Category("Action")]
        [Description("Událost vyvolaná v průběhu změny hodnoty Ratio posuvníku.")]
        public event RatioChangeHandler RatioChanged;
        #endregion
        #region IMPLEMENTACE PROPERTY A EVENTŮ
        private bool _SilentChange;
        private int _Resolution;
        private double? _Ratio;
        private void _ResolutionGet()
        {
            if (this.InvokeRequired)
                this.Invoke(new Action(this._ResolutionGet));
            else
            	// Zaručeně v owner threadu:
                this._Resolution = (this.Maximum - this.Minimum);
        }
        private void _ResolutionSet()
        {
            if (this.InvokeRequired)
                this.Invoke(new Action(this._RatioSet));
            else
            {	// Zaručeně v owner threadu:
                // Změna rozlišení nemá způsobit změnu viditelné hodnoty (ratio), takže na začátku si přečtu aktuální ratio, 
                // a po změně Minimum a Maximum nastavím zpátky původní Ratio, čímž se přepočítá Value (silent = bez eventu RatioChange)
                int resolution = this._Resolution;  // Hodnotu (this._Resolution) musím číst hned na začátku, před tím než začnu pracovat s dalšími hodnotami
                double? ratio = this.Ratio;         // Čtení Ratio vyhodnocuje aktuální Resolution a Value
                this.Minimum = 0;
                this.Maximum = resolution;          // Nastavím fyzické provedení pro danou Resolution
                this.RatioSilent = ratio;           // Nastavím Value, ale nevolám event RatioChanged
            }
        }
		private void _RatioGet()
		{
			if (this.InvokeRequired)
				this.Invoke(new Action(this._RatioGet));
			else
			{	// Zaručeně v owner threadu:
				this._Ratio = null;
				int res = this.Maximum - this.Minimum;
				if (res <= 0) return;
				double resolution = (double)res;
				double size = (double)(resolution);
                double ratio = (double)(this.Value - this.Minimum) / size;
                bool revert = (this.Orientation == System.Windows.Forms.Orientation.Vertical);        // Při orientaci Vertical je hodnota Value == 0 nahoře, ale my chceme mít Ratio == 0.0 dole !!
                this._Ratio = (revert ? 1D - ratio : ratio);
			}
		}
		private void _RatioSet()
		{
			if (this.InvokeRequired)
				this.Invoke(new Action(this._RatioSet));
			else
			{	// Zaručeně v owner threadu:
                double? ratio = this._Ratio;
                if (!ratio.HasValue) return;

                int res = this.Maximum - this.Minimum;
				if (res <= 0) return;
				double resolution = (double)res;

                bool revert = (this.Orientation == System.Windows.Forms.Orientation.Vertical);        // Při orientaci Vertical je hodnota Value == 0 nahoře, ale my chceme mít Ratio == 0.0 dole !!
                double value = ratio.Value;
                double scale = (revert  ? 1D - value : value);
				if (scale < 0D) scale = 0D;
				if (scale > 1D) scale = 1D;
				double offs = scale * resolution;
				int position = (int)(Math.Round(((double)this.Minimum + offs), 0));
                // Nastavení Value vyvolá zdejší event _ValueChanged(), který detekuje hodnotu this._SilentChange - pokud je true (Silent), pak se nevyvolá handler RatioChanged:
                // Hodnota this._SilentChange je nastavena na true / false podle toho, zda se setuje RatioSilent (pak je true) anebo Ratio (pak je false).
				this.Value = position;
			}
		}
        /// <summary>
        /// Event po změně hodnoty
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void _ValueChanged(object sender, EventArgs e)
        {
            if (!this._SilentChange)
                OnRatioChanged();
        }
        /// <summary>
        /// Vyvolávač události RatioChanged
        /// </summary>
        /// <param name="scale"></param>
        protected virtual void OnRatioChanged()
        {
            if (this.RatioChanged != null)
            {
                double? ratio = this.Ratio;
                if (ratio.HasValue)
                    this.RatioChanged(this, new RatioChangeEventArgs(ratio.Value));
            }
        }
 		#endregion
	}
    #endregion
    #region DELEGATE RatioChangeHandler, CLASS RatioChangeEventArgs
    /// <summary>
    /// Předpis pro metody, které hrají roli handleru událostí změny hodnoty Ratio
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    public delegate void RatioChangeHandler(object sender, RatioChangeEventArgs e);
    /// <summary>
    /// Data popisující hodnotu Ratio
    /// </summary>
    public class RatioChangeEventArgs : EventArgs
    {
        public RatioChangeEventArgs()
        {
            this.Ratio = 0D;
        }
        public RatioChangeEventArgs(double ratio)
        {
            this.Ratio = (ratio < 0D ? 0D : (ratio > 1D ? 1D : ratio));
        }
        public static readonly RatioChangeEventArgs Empty = new RatioChangeEventArgs();
        /// <summary>
        /// Hodnota Ratio (v rozmezí 0 až 1)
        /// </summary>
        public double Ratio { get; private set; }
    }
    #endregion
}
